
#VlnPlot
# seurat-4.1.0/R/visualization.R:575:VlnPlot <- function(
# 这个函数不长，我们就好好看看。


#' Single cell violin plot
#'
#' Draws a violin plot of single cell data (gene expression, metrics, PC
#' scores, etc.)
#'
#' @inheritParams RidgePlot #啥意思？继承？
#' @param pt.size Point size for geom_violin
#' @param split.by A variable to split the violin plots by,
#' @param split.plot  plot each group of the split violin plots by multiple or
#' single violin shapes.
#' @param adjust Adjust parameter for geom_violin
#' @param flip flip plot orientation (identities on x-axis)
#' @param raster Convert points to raster format. Requires 'ggrastr' to be installed.
# default is \code{NULL} which automatically rasterizes if ggrastr is installed and
# number of points exceed 100,000. #点超过10万，且有 ggrastr 包自动栅格化点图。
#'
#' @return A \code{\link[patchwork]{patchwork}ed} ggplot object if
#' \code{combine = TRUE}; otherwise, a list of ggplot objects 
#' #返回一个 ggplot对象，如果 combine=T；否则返回一个ggplot对象list。
#'
#' @export
#' @concept visualization
#'
#' @seealso \code{\link{FetchData}}
#'
#' @examples
#' data("pbmc_small")
#' VlnPlot(object = pbmc_small, features = 'PC_1')
#' VlnPlot(object = pbmc_small, features = 'LYZ', split.by = 'groups')
#'
VlnPlot <- function(
  object,
  features,
  cols = NULL,
  pt.size = NULL,
  idents = NULL,
  sort = FALSE,
  assay = NULL,
  group.by = NULL,
  split.by = NULL,
  adjust = 1,
  y.max = NULL,
  same.y.lims = FALSE,
  log = FALSE,
  ncol = NULL,
  slot = 'data',
  split.plot = FALSE,
  stack = FALSE,
  combine = TRUE, #默认是合并这些图
  fill.by = 'feature',
  flip = FALSE,
  raster = NULL
) {
  # 如果设置了 split.by 参数，且环境变量 Seurat.warn.vlnplot.split 没设置过，则警告：该参数变了，请使用 split.plot=T
  if (
    !is.null(x = split.by) &
    getOption(x = 'Seurat.warn.vlnplot.split', default = TRUE)
  ) {
    message(
      "The default behaviour of split.by has changed.\n",
      "Separate violin plots are now plotted side-by-side.\n",
      "To restore the old behaviour of a single split violin,\n",
      "set split.plot = TRUE.
      \nThis message will be shown once per session."
    )
    # 设置全局变量，下次使用不会提醒；重启 R session 会再次提醒。
    options(Seurat.warn.vlnplot.split = FALSE)
  }

  # 返回的是 ExIPlot，继续追查
  return(ExIPlot(
    object = object,
    type = ifelse(test = split.plot, yes = 'splitViolin', no = 'violin'), #选一个参数，默认就是 violin
    features = features,
    idents = idents,
    ncol = ncol,
    sort = sort,
    assay = assay,
    y.max = y.max,
    same.y.lims = same.y.lims,
    adjust = adjust, #默认 1
    pt.size = pt.size,
    cols = cols,
    group.by = group.by,
    split.by = split.by,
    log = log,
    slot = slot, #默认 'data'
    stack = stack,
    combine = combine,
    fill.by = fill.by, #默认'feature',
    flip = flip,
    raster = raster
  ))
}








# seurat-4.1.0/R/visualization.R:5558:ExIPlot <- function( 这个R文件超过5千行了？

# Plot feature expression by identity
#
# Basically combines the codebase for VlnPlot and RidgePlot #合并了这2个图的代码
#
# @param object Seurat object
# @param type Plot type, choose from 'ridge', 'violin', or 'splitViolin' #共3种图
# @param features Features to plot (gene expression, metrics, PC scores,
# anything that can be retreived by FetchData) # FetchData 函数能拿到的数据
# @param idents Which classes to include in the plot (default is all)
# @param ncol Number of columns if multiple plots are displayed
# @param sort Sort identity classes (on the x-axis) by the average expression of the attribute being potted,
# or, if stack is True, sort both identity classes and features by hierarchical clustering 
# sort 排序，要么按照每个ident的平均表达量(x轴)排序，要么stack=T时按照表达量和层次聚类排序

# @param y.max Maximum y axis value #最大y值
# @param same.y.lims Set all the y-axis limits to the same values #所有y轴的上下限都一样
# @param adjust Adjust parameter for geom_violin
# @param pt.size Point size for geom_violin
# @param cols Colors to use for plotting
# @param group.by Group (color) cells in different ways (for example, orig.ident)
# @param split.by A variable to split the plot by #分面
# @param log plot Y axis on log scale #y轴使用log尺度
# @param slot Use non-normalized counts data for plotting #
# @param stack Horizontally stack plots for multiple feature # 多个 feature 时，水平堆叠
# @param combine Combine plots into a single \code{\link[patchwork]{patchwork}ed}
# ggplot object. If \code{FALSE}, return a list of ggplot objects #合并就是返回一个ggplot对象；否则就是返回一个ggplot对象列表
# @param fill.by Color violins/ridges based on either 'feature' or 'ident' #颜色根据 feature or ident
# @param flip flip plot orientation (identities on x-axis) #是否替换xy轴
# @param raster Convert points to raster format, default is \code{NULL} which #超过10万个点自动栅格化
# automatically rasterizes if plotting more than 100,000 cells
#
# @return A \code{\link[patchwork]{patchwork}ed} ggplot object if
# \code{combine = TRUE}; otherwise, a list of ggplot objects #返回值取决于 combine 参数
#
#' @importFrom scales hue_pal
#' @importFrom ggplot2 xlab ylab
#' @importFrom patchwork wrap_plots
#
ExIPlot <- function(
  object,
  features,
  type = 'violin',
  idents = NULL,
  ncol = NULL,
  sort = FALSE,
  assay = NULL,
  y.max = NULL,
  same.y.lims = FALSE,
  adjust = 1,
  cols = NULL,
  pt.size = 0,
  group.by = NULL,
  split.by = NULL,
  log = FALSE,
  slot = 'data',
  stack = FALSE,
  combine = TRUE,
  fill.by = NULL,
  flip = FALSE,
  raster = NULL
) {
  # 获取 assay，如果没有就使用默认:"RNA"
  assay <- assay %||% DefaultAssay(object = object)
  # 设定 DefaultAssay 的值
  DefaultAssay(object = object) <- assay


  # 如果 stack=T
  if (isTRUE(x = stack)) {

    # 如果ncol非空，则提醒忽略ncol
    if (!is.null(x = ncol)) {
      warning(
        "'ncol' is ignored with 'stack' is TRUE",
        call. = FALSE,
        immediate. = TRUE
      )
    }

    # 如果y.max非空，则提醒忽略y.max
    if (!is.null(x = y.max)) {
      warning(
        "'y.max' is ignored when 'stack' is TRUE",
        call. = FALSE,
        immediate. = TRUE
      )
    }

  # 如果 stack=F
  } else {
    # 如果 ncol 为空，则设置默认值：基因数>9则选4，否则为 基因数和3中的最小值。
    ncol <- ncol %||% ifelse(
      test = length(x = features) > 9,
      yes = 4,
      no = min(length(x = features), 3)
    )
  }


  ###############
  # 获取数据： slot="data" 默认，features 指定的几个属性。返回的是df，行为cell id,列为features
  data <- FetchData(object = object, vars = features, slot = slot) #这个函数比较复杂，下节讲 //todo




  pt.size <- pt.size %||% AutoPointSize(data = object) #点的大小，如果不指定就自动指定

  features <- colnames(x = data) #重新获取列名 //前2行刚用过 features，这为什么又获取呢？因为有些行可能没有数据。

  # 如果没有 idents，cells就是所有 cell id
  if (is.null(x = idents)) {
    cells <- colnames(x = object)
  } else {
    # 否则，只获取在这些 idents 中的cell id
    cells <- names(x = Idents(object = object)[Idents(object = object) %in% idents])
  }

  # 使用 cells取子集，不能失去df结构
  data <- data[cells, , drop = FALSE]

  # 如果 group.by 为空，则idents就是idents，name是cid
  idents <- if (is.null(x = group.by)) {
    Idents(object = object)[cells]
  } else {
    # 否则，就是按 group.by 指定的meta.data 为值，name是cid
    object[[group.by, drop = TRUE]][cells]
    # > head( pbmc_small[["groups", drop=T]] )
    # ATGCCAGAACGACT CATGGCCTGTGCAT GAACCTGATGAACC TGACTGGATTCTCA AGTCAGACTGCACA TCTGATACACGTGT 
    #      "g2"           "g1"           "g2"           "g2"           "g2"           "g1"
  }


  # 强制 idents 变为 factor
  if (!is.factor(x = idents)) {
    idents <- factor(x = idents)
  }

  # 如果 split.by 为空，则 split赋值为空
  if (is.null(x = split.by)) {
    split <- NULL

  # 如果 split.by 不为空
  } else {
    # 先获取 meta.data$split.by，名字为cell id，保持df结构；按照cells获取子集
    split <- object[[split.by, drop = TRUE]][cells] #左值为 name为cid的 meta.data$ split.by的值
    
    # split 强制变为 factor
    if (!is.factor(x = split)) {
      split <- factor(x = split)
    }

    ####### 
    # 处理颜色参数 cols
    # 如果没有指定颜色，就自动生成颜色
    if (is.null(x = cols)) {
      cols <- hue_pal()(length(x = levels(x = idents))) #自动获取n个16进制的颜色
      cols <- Interleave(cols, InvertHex(hexadecimal = cols)) # 出现1次
      # 生成互补色，然后互相交替展开成一个列向量，颜色是不是多了一半?
    

    # 如果指定颜色了，但是长度为1，且值为 interaction
    } else if (length(x = cols) == 1 && cols == 'interaction') {
      split <- interaction(idents, split) # 获得交互因素，干啥用的？ //todo 下文第三节有
      cols <- hue_pal()(length(x = levels(x = idents))) #自动获取n个16进制的颜色

    # 否则，就是指定了好几个颜色：把R颜色转为16进制形式，带alpha位
    } else {
      cols <- Col2Hex(cols)
    }


    # 如果cols长度小于 split 的levels
    if (length(x = cols) < length(x = levels(x = split))) {
      cols <- Interleave(cols, InvertHex(hexadecimal = cols)) #出现2次 和上文重复，上文那个是否可以删掉？
    }

    # split 有几个level，原 cols 就重复几次
    cols <- rep_len(x = cols, length.out = length(x = levels(x = split)))

    # 颜色的name为 split 的level
    names(x = cols) <- levels(x = split)

    # 如果颜色>2， 且画 splitViolin，提示: 只支持<3。修改tye为普通 violin
    if ((length(x = cols) > 2) & (type == "splitViolin")) {
      warning("Split violin is only supported for <3 groups, using multi-violin.")
      type <- "violin"
    }
  } # end of else;




  # 如果设置 sam.y.lims=T 且 y.max 为空
  if (same.y.lims && is.null(x = y.max)) {
    y.max <- max(data) #则主动获取 y.max
  }


  # 如果堆叠，返回MultiExIPlot()，该绘图函数结束。
  if (isTRUE(x = stack)) {
    return(MultiExIPlot( #这个函数先跳过，以后遇到再说
      type = type,
      data = data,
      idents = idents,
      split = split,
      sort = sort,
      same.y.lims = same.y.lims,
      adjust = adjust,
      cols = cols,
      pt.size = pt.size,
      log = log,
      fill.by = fill.by,
      flip = flip
    ))
  }


  # 如果不堆叠
  # 对 features 的每一个元素，获取 SingleExIPlot()，最终是一个ggplot对象的 list
  plots <- lapply(
    X = features, #对每一个 feature 循环，进入FUN画图
    FUN = function(x) {
      return(SingleExIPlot( #先说说这个函数，也很长
        type = type,
        data = data[, x, drop = FALSE], #只有一列的df
        idents = idents,
        split = split,

        sort = sort,
        y.max = y.max,
        adjust = adjust,
        cols = cols,
        pt.size = pt.size,
        log = log,
        raster = raster
      ))
    }
  )



  # 设置 label.fxn 的2个值，xlab or ylab
  label.fxn <- switch(
    EXPR = type,

    'violin' = if (stack) {
      xlab
    } else {
      ylab
    },

    "splitViolin" = if (stack) {
      xlab
    } else {
      ylab
    },

    'ridge' = xlab,
    stop("Unknown ExIPlot type ", type, call. = FALSE)
  )

  
  # 对 splots 中的每个元素下标
  for (i in 1:length(x = plots)) {

    # 每个基因，使用_分割后，unlist，取第一个元素，然后后面加上_
    key <- paste0(unlist(x = strsplit(x = features[i], split = '_'))[1], '_')
    # > paste0(unlist(x = strsplit(x = "PC_1", split = '_'))[1], '_')
    # [1] "PC_" #这里可能不是为了处理基因，是为了处理 PC_2, tSNE_1 这种

    # 然后获取 Key() 的输出中，和 key一样的子对象的 name属性
    # 看来这个 Key() 函数就是提供 对象名 pca 和 对象中 key slot 的值 "PC_" 的一一对应关系。
    obj <- names(x = which(x = Key(object = object) == key)) # //todo 这句有点复杂，干啥呢？
    # > Key(object = pbmc_small)
    # RNA     pca    tsne 
    # "rna_"   "PC_" "tSNE_"
    # 
    # > Key(object = pbmc_small)=="PC_"
    # RNA   pca  tsne 
    # FALSE  TRUE FALSE 
    #
    # which( Key(object = pbmc_small)=="PC_" )
    # pca 
    #   2 
    # 最后的 obj 就是字符串 "pca"


    # 如果 obj 长度是1，修改坐标轴标签
    if (length(x = obj) == 1) {
      # 如果该对象是 降维对象
      if (inherits(x = object[[obj]], what = 'DimReduc')) {
        # 定义坐标轴标签
        plots[[i]] <- plots[[i]] + label.fxn(label = 'Embeddings Value')
        # 测试 
        # VlnPlot(object = pbmc_small, features = c('PC_1', "tSNE_2") ) #看ylab
        # VlnPlot(object = pbmc_small, features = c('CD3D') )  #看ylab
      } else if (inherits(x = object[[obj]], what = 'Assay')) {
        next #如果是 Assay 对象，则啥都不做，直接循环下一个变量
      } else {
        # 如果是其他类型的变量，则警告，且坐标轴不加标签
        warning("Unknown object type ", class(x = object), immediate. = TRUE, call. = FALSE)
        plots[[i]] <- plots[[i]] + label.fxn(label = NULL)
      }
    # 如果 obj 长度不是1
    # 且当前 feature 不是基因symbol：坐标轴不加标签
    } else if (!features[i] %in% rownames(x = object)) {
      plots[[i]] <- plots[[i]] + label.fxn(label = NULL)
    }
  }


  # 如果有 combine 参数
  # 则把 ggplot2 list使用 patchwork::wrap_plots() 包裹好返回
  if (combine) {
    plots <- wrap_plots(plots, ncol = ncol)

    # 如果有多个 feature，则不加图例，&表示每个小图都不加图例
    if (length(x = features) > 1) {
      plots <- plots & NoLegend()
    }
  }
  # 如果 combine=F，则返回 ggplot2 list

  return(plots)
}






#$ find .  | grep "R$" | xargs grep -n "DefaultAssay<-" --color=auto
#seurat-object-4.0.4/R/generics.R:216:"DefaultAssay<-" <- function(object, ..., value) {
#seurat-object-4.0.4/R/seurat.R:1135:"DefaultAssay<-.Seurat" <- function(object, ..., value) {


#' @param value Name of assay to set as default
#'
#' @return \code{DefaultAssay<-}: An object with the default assay updated
#'
#' @rdname DefaultAssay
#' @export DefaultAssay<-
#'
"DefaultAssay<-" <- function(object, ..., value) {
  UseMethod(generic = 'DefaultAssay<-', object = object) #S3泛型函数定义
}




# 函数实现，这个带<-符号的是写入函数，它的右参数是参数列表的最后一个

#' @rdname DefaultAssay
#' @export
#' @method DefaultAssay<- Seurat
#'
#' @examples
#' # Create dummy new assay to demo switching default assays
#' new.assay <- pbmc_small[["RNA"]]
#' Key(object = new.assay) <- "RNA2_"
#' pbmc_small[["RNA2"]] <- new.assay
#' # switch default assay to RNA2
#' DefaultAssay(object = pbmc_small) <- "RNA2"
#' DefaultAssay(object = pbmc_small)
#'
"DefaultAssay<-.Seurat" <- function(object, ..., value) { #等号右侧的值是最后一个参数
  CheckDots(...)
  object <- UpdateSlots(object = object) #使用slot中的老数据，重新生成对象(见 解析 7 2.4)

  # 如果在 obj@assays 这个list中不存在 value 这个名字，则报错
  if (!value %in% names(x = slot(object = object, name = 'assays'))) {
    stop("Cannot find assay ", value)
  }

  # 否则修改 obj@active.assay 为该 value 字符串
  slot(object = object, name = 'active.assay') <- value
  return(object)
}












#. SingleExIPlot() 
# 在 visualization.R 7218行:


#' Plot a single expression by identity on a plot 单个的表达图，按ident划分
#'
#' @param data Data to plot
#' @param idents Idents to use
#' @param split Use a split violin plot
#' @param type Make either a \dQuote{ridge} or \dQuote{violin} plot
#' @param sort Sort identity classes (on the x-axis) by the average
#' expression of the attribute being potted
#' @param y.max Maximum Y value to plot
#' @param adjust Adjust parameter for geom_violin
#' @param pt.size Size of points for violin plots
#' @param cols Colors to use for plotting
#' @param seed.use Random seed to use. If NULL, don't set a seed
#' @param log plot Y axis on log scale
#' @param raster Convert points to raster format. Requires 'ggrastr' to be installed.
#' default is \code{NULL} which automatically rasterizes if ggrastr is installed and
#' number of points exceed 100,000.
#'
#' @return A ggplot-based Expression-by-Identity plot
#'
#' @importFrom stats rnorm
#' @importFrom utils globalVariables
#' @importFrom ggridges geom_density_ridges theme_ridges
#' @importFrom ggplot2 ggplot aes_string theme labs geom_violin geom_jitter
#' ylim position_jitterdodge scale_fill_manual scale_y_log10 scale_x_log10
#' scale_y_discrete scale_x_continuous waiver
#' @importFrom cowplot theme_cowplot
#'
#' @keywords internal #内部函数
#' @export #暴露出去
#'
SingleExIPlot <- function(
  data,
  idents,
  split = NULL,
  type = 'violin',
  sort = FALSE,
  y.max = NULL,
  adjust = 1,
  pt.size = 0,
  cols = NULL,
  seed.use = 42,
  log = FALSE,
  raster = NULL
) {

   # raster 非空，且是 TRUE
   if (!is.null(x = raster) && isTRUE(x = raster)){
    # 如果没有安装 ggrastr 包，则报错退出
    if (!PackageCheck('ggrastr', error = FALSE)) {
      stop("Please install ggrastr from CRAN to enable rasterization.")
    }
  }

  # 如果安装了 ggrastr 包，则点个数超过10万，且raster参数不是F，则设置为栅格化
  if (PackageCheck('ggrastr', error = FALSE)) {
    # Set rasterization to true if ggrastr is installed and
    # number of points exceeds 100,000
    if ((nrow(x = data) > 1e5) & !isFALSE(raster)){
      message("Rasterizing points since number of points exceeds 100,000.",
              "\nTo disable this behavior set `raster=FALSE`")
    }
    raster <- TRUE
  }


  # 如果随机数种子非空，就使用种子
  if (!is.null(x = seed.use)) {
    set.seed(seed = seed.use)
  }


  # 如果data不是df，或者 列数不等于1，则报错。
  # 只有一列是啥时候实现的？ 调用这个而函数的 lapply中
  if (!is.data.frame(x = data) || ncol(x = data) != 1) {
    stop("'SingleExIPlot requires a data frame with 1 column")
  }

  # 取只有一列的 feature 名字
  feature <- colnames(x = data)

  # 给df增加一列 ident列
  data$ident <- idents

  # 如果 sort 是字符串， 且字符数>0 或者 是逻辑值T
  if ((is.character(x = sort) && nchar(x = sort) > 0) || sort) {

    # ident 列转为因子，对levels排序
    data$ident <- factor(
      x = data$ident,
      levels = names(x = rev(x = sort( # 按均值排序，小写后的sort能匹配到"decreasing"则降序
        # 按照 ident 对 feature列分割，取每组平均值
        x = tapply(
          X = data[, feature],
          INDEX = data$ident,
          FUN = mean
        ),
        decreasing = grepl(pattern = paste0('^', tolower(x = sort)), x = 'decreasing')
      )))
    )
  }
  # 


  # 随机数种子在这里起作用。
  #如果 log=T
  if (log) {
    # 增加一些正态分布N(0,1)随机数，除以 200
    noise <- rnorm(n = length(x = data[, feature])) / 200
    data[, feature] <- data[, feature] + 1 #加1防止log报错
  } else {
    # 增加一些正态分布N(0,1)随机数，除以 1e5
    noise <- rnorm(n = length(x = data[, feature])) / 100000
  }
  

  # 一列值相同就不增加噪音了！不相同则增加噪音
  # 传入该函数的 feature只有一个字符长度，
  if (all(data[, feature] == data[, feature][1])) {
    # 如果所有细胞的值都和第一个相等，则可能有问题，警告
    warning(paste0("All cells have the same value of ", feature, "."))
  } else{
    # 否则就增加噪音。
    data[, feature] <- data[, feature] + noise
  }

  # 坐标轴标签
  axis.label <- 'Expression Level'


  # y.max 如果没设置，就取这一列中去掉无穷大之后的最大值
  y.max <- y.max %||% max(data[, feature][is.finite(x = data[, feature])])


  # 如果类型是 violin，且split非空
  if (type == 'violin' && !is.null(x = split)) {
    data$split <- split
    vln.geom <- geom_violin
    fill <- 'split'

  # 如果是 splitViolin，其 split 不能为空
  } else if (type == 'splitViolin' && !is.null(x = split )) {
    data$split <- split
    vln.geom <- geom_split_violin
    fill <- 'split'
    type <- 'violin'
  # 否则，普通 violin，按ident填充
  } else {
    vln.geom <- geom_violin
    fill <- 'ident'
  }


  # 按类型设置，画图的关键语句
  switch(
    EXPR = type,

    # 
    'violin' = {
      x <- 'ident'
      y <- paste0("`", feature, "`")
      xlab <- 'Identity'
      ylab <- axis.label
      geom <- list(
        vln.geom(scale = 'width', adjust = adjust, trim = TRUE), #geom_violin 的这几个参数
        theme(axis.text.x = element_text(angle = 45, hjust = 1)) #x轴旋转45度
      )




      # 如果 split 空
      if (is.null(x = split)) {
        # 如果 raster=T
        if (isTRUE(x = raster)) {
          # 栅格化点图
          jitter <- ggrastr::rasterize(geom_jitter(height = 0, size = pt.size, show.legend = FALSE))
        } else {
          # 不栅格化点图
          jitter <- geom_jitter(height = 0, size = pt.size, show.legend = FALSE)
        }

      # 如果 split 非空
      } else {
        # 如果 taster=T
        if (isTRUE(x = raster)) {
          # 栅格化点图
          jitter <- ggrastr::rasterize(geom_jitter(
            position = position_jitterdodge(jitter.width = 0.4, dodge.width = 0.9),
            size = pt.size,
            show.legend = FALSE
          ))
        } else {
          # 不栅格化点图
          jitter <- geom_jitter(
            position = position_jitterdodge(jitter.width = 0.4, dodge.width = 0.9),
            size = pt.size,
            show.legend = FALSE
          )
        }
      } # end of if



      log.scale <- scale_y_log10()
      axis.scale <- ylim
    },


    # 跳过去，以后有时间再看
    'ridge' = {
      x <- paste0("`", feature, "`")
      y <- 'ident'
      xlab <- axis.label
      ylab <- 'Identity'
      geom <- list(
        geom_density_ridges(scale = 4),
        theme_ridges(),
        scale_y_discrete(expand = c(0.01, 0)),
        scale_x_continuous(expand = c(0, 0))
      )
      jitter <- geom_jitter(width = 0, size = pt.size, show.legend = FALSE)
      log.scale <- scale_x_log10()
      axis.scale <- function(...) {
        invisible(x = NULL)
      }
    },
    stop("Unknown plot type: ", type)
  )
  

  ##############
  # 画图核心句子
  plot <- ggplot(
    data = data,
    mapping = aes_string(x = x, y = y, fill = fill)[c(2, 3, 1)] #这个2,3,1干啥的？不知道 //todo
  ) +
    labs(x = xlab, y = ylab, title = feature, fill = NULL) +
    theme_cowplot() +
    theme(plot.title = element_text(hjust = 0.5))


  # 还能这样给ggplot2添加批量修改
  plot <- do.call(what = '+', args = list(plot, geom)) 

  # 如果参数log=T，则使用对数坐标，否则限制y轴的最值
  # 就是说log尺度就不限制y轴的最值了？！
  plot <- plot + if (log) {
    log.scale
  } else {
    axis.scale(min(data[, feature]), y.max)
  }

  # 点的大小，如果是0，就跳过；大于0，就画点图：
  if (pt.size > 0) {
    plot <- plot + jitter
  }
  
  # 如果无自定义颜色，直接最后一行返回：我们一般都没有颜色
  # 如果有自定义颜色
  if (!is.null(x = cols)) {

    # 如果 split 非空
    if (!is.null(x = split)) {
      idents <- unique(x = as.vector(x = data$ident)) #拿到uniq idents 值
      splits <- unique(x = as.vector(x = data$split)) #拿到uniq split 值

      # 如果 split 长度是2，就赋值给 labels
      labels <- if (length(x = splits) == 2) {
        splits
      } else {
        # 如果 split 长度不是2
        unlist(x = lapply(
          X = idents, #对每一个idents 进行循环


          FUN = function(pattern, x) {
            # 全局替换.为": "，不用正则，字面量替换
            x.mod <- gsub(
              # 为什么基因名字中有点？是重名基因吗？ CD4, CD4.1？
              # 不是！ 是前面的 ident和split的交互项
              pattern = paste0(pattern, '.'), 
              replacement = paste0(pattern, ': '),
              x = x,
              fixed = TRUE
            )
            
            x.keep <- grep(pattern = ': ', x = x.mod, fixed = TRUE)
            x.return <- x.mod[x.keep] #只保留带": "的项目

            names(x = x.return) <- x[x.keep] # 给交互项命名为：split的名字
            return(x.return)
          },


          x = unique(x = as.vector(x = data$split)) #为方程传入更多参数，uniq split 值
        ))
      }

      # 如果 labels 没有name，则自己给自己命名
      if (is.null(x = names(x = labels))) {
        names(x = labels) <- labels
      }

    # 如果split 为空
    } else {
      labels <- levels(x = droplevels(data$ident))
    }

    # 添加自定义颜色和图例文字
    plot <- plot + scale_fill_manual(values = cols, labels = labels)
  }


  return(plot)
}

